﻿// Copyright 2012 T4C: sejerpz@gmail.com
// Licensed under GPL v3

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Reflection;
using System.Globalization;
using SET.Core;
using SET.Excercises;
using SET.Statistics;
using System.IO;

namespace SET
{
    public partial class FormMain : Form
    {
        private Intervals intervals = new Intervals();
        private GlobalKeyboardHook keybHook;
        private bool started = false;
        private bool waitingUserResponse = false;
        private bool replayInterval = false;
        private Database statistics;

        private int pendingAnswer = -1;

        public FormMain()
        {
            InitializeComponent();
            string userAppDataPath = Application.UserAppDataPath.Substring(0, Application.UserAppDataPath.Length - Application.ProductVersion.ToString().Length);
             
            statistics = new Database(Path.Combine(userAppDataPath, "SET.db"));
            var assembly = typeof(FormMain).Assembly;
            labelAboutInfo.Text = string.Format(Messages.ResourceManager.GetString("{0} by {1} {2}".ToValidIdentifier()),
                ((AssemblyDescriptionAttribute)assembly.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false)[0]).Description,
                ((AssemblyCompanyAttribute)assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false)[0]).Company,
                ((AssemblyCopyrightAttribute)assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false)[0]).Copyright);

            labelAboutVer.Text = string.Format(Messages.ResourceManager.GetString("Version: {0}".ToValidIdentifier()), Application.ProductVersion.ToString());
            Defaults();
            PlayHelloMessage();
            RegisterGlobalInputKeyHandler();
            intervals.IntervalPlaying += new EventHandler(intervals_IntervalPlaying);
            intervals.IntervalPlayed += new EventHandler<NoteIntervalEventArgs>(intervals_IntervalPlayed);
            Application.Idle += new EventHandler(Application_Idle);
            this.Visible = false;
        }



        private void PlayHelloMessage()
        {
            var play = new PlayMidi();
            int durationMs = 80;
            play.PlayNotes(4, new string[] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C" }, durationMs, 1000);
        }

        private bool Started
        {
            get { return started; }
            set
            {
                if (started != value)
                {
                    var pv = started;

                    started = value;
                    if (started)
                        Start();
                    else if (pv == true)
                        Stop();
                }

                toolStripMenuItemStart.Enabled = !started;
                toolStripMenuItemStop.Enabled = started;
                toolStripMenuItemAnswer.Enabled = started;
            }
        }

        private void Application_Idle(object sender, EventArgs e)
        {
            Application.Idle -= new EventHandler(Application_Idle);
            this.Hide();
            string message = string.Format(Messages.ResourceManager.GetString("Right Click to configure and start, press '{0}' to answer.".ToValidIdentifier()), Properties.Settings.Default.ResposeKey);
            notifyIcon.ShowBalloonTip(500, Application.ProductName, message, ToolTipIcon.Info);
            Application.Idle += new EventHandler(answerDispatcher_Idle);
        }

        private void answerDispatcher_Idle(object sender, EventArgs e)
        {
            if (pendingAnswer != -1)
            {
                Answer(pendingAnswer);
                pendingAnswer = -1;
            }
        }

        private void intervals_IntervalPlaying(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(this.IntervalPlaying),  null);
            }
            else
            {
                IntervalPlaying();
            }
        }

        private void intervals_IntervalPlayed(object sender, NoteIntervalEventArgs e)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action<NoteIntervalEventArgs>(this.IntervalPlayed), new object [] {e});
            }
            else
            {
                IntervalPlayed(e);
            }
        }

        private void IntervalPlaying()
        {
            if (replayInterval)
                notifyIcon.Icon = SET.Properties.Resources.replay16;
            else
                notifyIcon.Icon = SET.Properties.Resources.play16;
        }

        private void IntervalPlayed(NoteIntervalEventArgs e)
        {
            if (replayInterval)
            {
                replayInterval = false;
            }
            notifyIcon.Icon = SET.Properties.Resources.app16;
            waitingUserResponse = true;
            notifyIcon.BalloonTipIcon = ToolTipIcon.Info;
            notifyIcon.BalloonTipTitle = string.Format(Messages.ResourceManager.GetString("Last played interval (Press '{0}' to answer)".ToValidIdentifier()), Properties.Settings.Default.ResposeKey);
            notifyIcon.BalloonTipText = e.Interval.ToString();
            notifyIcon.Text = string.Format(Messages.ResourceManager.GetString("Last interval: {0} (Press '{1}' to answer)".ToValidIdentifier()), e.Interval.ToString(), Properties.Settings.Default.ResposeKey);
            toolStripMenuItemAnswer.Enabled = true;
        }

        private void RegisterGlobalInputKeyHandler()
        {
            keybHook = new GlobalKeyboardHook();
         
            keybHook.KeyUp += new KeyEventHandler(keybHook_KeyUp);
        }

        /* 
         * This routine should be very fast, otherwise we will be disconnected from the
         * global keyboard hooks
         */
        private void keybHook_KeyUp(object sender, KeyEventArgs e)
        {
            if (!Started || !waitingUserResponse)
                return;

            int val = 0;
            bool responseModifiersDown = (Control.ModifierKeys & Keys.Control) != 0;
            bool adderModifiersDown = false;
            
            if (responseModifiersDown)
            {
                adderModifiersDown = (Control.ModifierKeys & Keys.Alt) != 0;
                e.Handled = true;

                switch (e.KeyCode)
                {
                    case Keys.D0:
                    case Keys.NumPad0:
                    case Keys.Insert:
                        val = 0;
                        break;
                    case Keys.D1:
                    case Keys.NumPad1:
                    case Keys.End:
                        val = 1;
                        break;
                    case Keys.D2:
                    case Keys.NumPad2:
                    case Keys.Down:
                        val = 2;
                        break;
                    case Keys.D3:
                    case Keys.NumPad3:
                    case Keys.PageDown:
                        val = 3;
                        break;
                    case Keys.D4:
                    case Keys.NumPad4:
                    case Keys.Left:
                        val = 4;
                        break;
                    case Keys.D5:
                    case Keys.NumPad5:
                        val = 5;
                        break;
                    case Keys.D6:
                    case Keys.NumPad6:
                    case Keys.Right:
                        val = 6;
                        break;
                    case Keys.D7:
                    case Keys.NumPad7:
                    case Keys.Clear:
                        val = 7;
                        break;
                    case Keys.D8:
                    case Keys.NumPad8:
                    case Keys.Up:
                        val = 8;
                        break;
                    case Keys.D9:
                    case Keys.NumPad9:
                    case Keys.PageUp:
                        val = 9;
                        break;
                    default:
                        e.Handled = false;
                        break;
                }
            }
            else
            {
                if (e.KeyCode == (Keys)checkBoxResponseKey.Tag)
                {
                    using (var input = new FormReponseInput())
                    {
                        var result = input.ShowDialog();
                        if (result == System.Windows.Forms.DialogResult.OK)
                        {
                            val = input.Interval;
                            e.Handled = true;
                        }
                        else if (result == System.Windows.Forms.DialogResult.Retry)
                        {
                            val = 19;
                            e.Handled = true;
                        }
                    }
                }
            }

            if (e.Handled)
            {
                if (adderModifiersDown)
                    val += 10;

                pendingAnswer = val; // schedule the answer in the idle application
            }
        }

        private void Answer(int interval)
        {
            if (interval > 12)
            {
                if (interval >= 17)
                {
                    replayInterval = true;
                    intervals.RepeatLast();
                }
                else
                    notifyIcon.ShowBalloonTip(500);
            }
            else
            {
                toolStripMenuItemAnswer.Enabled = false;
                waitingUserResponse = false;
                statistics.InsertStat(Excercise.Intervals, intervals.LastPlayedInterval.Serialize(), intervals.CorrectResponse.ToString(), interval.ToString());
                if (intervals.EvaluateResponse(interval))
                {
                    notifyIcon.Icon = SET.Properties.Resources.ok16;
                    intervals.PlayNew();
                }
                else
                {
                    notifyIcon.Icon = SET.Properties.Resources.ko16;
                    replayInterval = true;
                    intervals.RepeatLast();
                }
            }
        }

        private void Defaults()
        {
            Started = false;
            LoadSettings();
        }

        private void buttonHide_Click(object sender, EventArgs e)
        {
            this.Hide();
        }

        private void Start()
        {
            try
            {
                var distances = new List<int>();

                foreach (string item in toolStripTextBoxCustomInterval.Text.Split(',', ' ', ';'))
                {
                    if (string.IsNullOrEmpty(item))
                        continue;

                    int distance;
                    if (!int.TryParse(item, out distance))
                    {
                        throw new ArgumentException(Messages.ResourceManager.GetString("Configure a valid interval").ToValidIdentifier());
                    }
                    distances.Add(distance);
                }

                if (distances.Count < 2)
                {
                    throw new ArgumentException(Messages.ResourceManager.GetString("Configure at least 2 intervals").ToValidIdentifier());
                }
                intervals.Distances = distances.ToArray();
                var directions = new List<Direction>();
                if (toolStripMenuItemDirectionAscending.Checked)
                    directions.Add(Direction.Ascending);
                if (toolStripMenuItemDirectionDescending.Checked)
                    directions.Add(Direction.Descending);

                if (directions.Count == 0)
                {
                    toolStripMenuItemDirectionAscending.Checked = true;
                    directions.Add(Direction.Ascending);
                }

                intervals.Directions = directions.ToArray();
                notifyIcon.ShowBalloonTip(1000, Application.ProductName, string.Format(Messages.ResourceManager.GetString("Started: {0}".ToValidIdentifier()), toolStripTextBoxCustomInterval.Text), ToolTipIcon.Info);
                Started = true;
                intervals.Start();
            }
            catch (ArgumentException a)
            {
                MessageBox.Show(a.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format(Messages.ResourceManager.GetString("Exception: {0}".ToValidIdentifier()), ex.Message));
            }
        }

        private void Stop()
        {
            try
            {
                intervals.Stop();
                notifyIcon.ShowBalloonTip(1000, Application.ProductName, Messages.ResourceManager.GetString("Stopped".ToValidIdentifier()), ToolTipIcon.Info);
                Started = false;
            }
            catch (ArgumentException a)
            {
                MessageBox.Show(a.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(string.Format(Messages.ResourceManager.GetString("Exception: {0}".ToValidIdentifier()), ex.Message));
            }
        }


        private void toolStripMenuItemAbout_Click(object sender, EventArgs e)
        {
            this.Show();
            this.WindowState = FormWindowState.Normal;
            this.BringToFront();
        }

        private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.ApplicationExitCall)
            {
                if (MessageBox.Show(string.Format(Messages.ResourceManager.GetString("Close {0}?".ToValidIdentifier()), Application.ProductName), "", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) != System.Windows.Forms.DialogResult.Yes)
                    e.Cancel = true;
            }
        }

        private void FormMain_Resize(object sender, EventArgs e)
        {
            if (this.Visible && this.WindowState == FormWindowState.Minimized)
            {
                this.Hide();
                this.WindowState = FormWindowState.Normal;
            }
        }

        private void toolStripMenuItemStart_Click(object sender, EventArgs e)
        {
            Started = true;
        }

        private void toolStripMenuItemStop_Click(object sender, EventArgs e)
        {
            Started = false;
        }

        private void toolStripMenuItemExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void intervalsQuickSettingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var menuSender = (ToolStripMenuItem)sender;
            

            if (menuSender.Checked)
            {
                string setting = (string)menuSender.Tag;
             
                toolStripTextBoxCustomInterval.Text = setting;
               
            }
        }

        private void notifyIcon_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                if (Started)
                {
                    if (!string.IsNullOrEmpty(notifyIcon.BalloonTipText))
                        notifyIcon.ShowBalloonTip(1000);
                }
                else
                {
                    Started = true;
                }
            }
        }

        private void toolStripMenuItemConfigureDirections_Click(object sender, EventArgs e)
        {
            if (!toolStripMenuItemDirectionAscending.Checked && !toolStripMenuItemDirectionDescending.Checked)
            {
                toolStripMenuItemDirectionAscending.Checked = true;
            }
            SaveSettings();
            TryRestart();
        }

        private void TryRestart()
        {
            var ps = Started;
            if (Started)
                Stop();

            if (ps)
                Start();
        }

        private void numericUpDownNoteOnTime_ValueChanged(object sender, EventArgs e)
        {
            if (numericUpDownNoteOnTime.Value != Properties.Settings.Default.NoteOnTime)
            {
                SaveSettings();
            }
        }

        private void numericUpDownNoteOffTime_ValueChanged(object sender, EventArgs e)
        {
            if (numericUpDownNoteOffTime.Value != Properties.Settings.Default.NoteOnTime)
            {
                SaveSettings();
            }
        }

        #region Load /Save Settings
        private void LoadSettings()
        {
            toolStripTextBoxCustomInterval.Text = Properties.Settings.Default.LastIntervalSet;

            // fallback
            if (string.IsNullOrEmpty(toolStripTextBoxCustomInterval.Text))
                toolStripTextBoxCustomInterval.Text = "1,2";


            toolStripMenuItemDirectionAscending.Checked = Properties.Settings.Default.LastAscendingStatus;
            toolStripMenuItemDirectionDescending.Checked = Properties.Settings.Default.LastDescendingStatus;

            // fallback
            if (!toolStripMenuItemDirectionAscending.Checked && !toolStripMenuItemDirectionDescending.Checked)
                toolStripMenuItemDirectionAscending.Checked = true;

            decimal val = Properties.Settings.Default.NoteOnTime;
            if (val < numericUpDownNoteOnTime.Minimum)
                val = numericUpDownNoteOnTime.Minimum;
            else if (val > numericUpDownNoteOnTime.Maximum)
                val = numericUpDownNoteOnTime.Maximum;

            numericUpDownNoteOnTime.Value = val;

            val = Properties.Settings.Default.NoteOffTime;
            if (val < numericUpDownNoteOffTime.Minimum)
                val = numericUpDownNoteOffTime.Minimum;
            else if (val > numericUpDownNoteOffTime.Maximum)
                val = numericUpDownNoteOffTime.Maximum;

            numericUpDownNoteOffTime.Value = val;

            
            Keys key;
            try
            {
                key = (Keys)Enum.Parse(typeof(Keys), Properties.Settings.Default.ResposeKey, true);
            }
            catch
            {
                key = Keys.F12;
            }
            checkBoxResponseKey.Tag = key;
            UpdateCheckBoxText();
        }

        private void SaveSettings()
        {
            Properties.Settings.Default.LastIntervalSet = toolStripTextBoxCustomInterval.Text;
            Properties.Settings.Default.LastAscendingStatus = toolStripMenuItemDirectionAscending.Checked;
            Properties.Settings.Default.LastDescendingStatus = toolStripMenuItemDirectionDescending.Checked;
            Properties.Settings.Default.NoteOnTime = numericUpDownNoteOnTime.Value;
            Properties.Settings.Default.NoteOffTime = numericUpDownNoteOffTime.Value;
            Properties.Settings.Default.Save();
        }

        private void ResetSettings()
        {
            Properties.Settings.Default.Reset();
            Properties.Settings.Default.Save();
            LoadSettings();
        }
        #endregion

        private void toolStripTextBoxCustomInterval_TextChanged(object sender, EventArgs e)
        {
            foreach (var item in toolStripMenuItemSettings.DropDownItems)
            {
                var menuItem = item as ToolStripMenuItem;
                if (menuItem != null && menuItem.Tag != null)
                {
                    if (menuItem.Tag.ToString() == toolStripTextBoxCustomInterval.Text)
                        menuItem.Checked = true;
                    else
                        menuItem.Checked = false;
                }
            }

            if (toolStripTextBoxCustomInterval.Text != Properties.Settings.Default.LastIntervalSet)
            {
                SaveSettings();
                TryRestart();
            }
        }

        private void tabControl_SelectedIndexChanged(object sender, EventArgs e)
        {
            buttonResetSettings.Visible = tabControl.SelectedTab == tabPageSettings;
        }

        private void buttonResetSettings_Click(object sender, EventArgs e)
        {
            ResetSettings();
        }

        private void checkBoxResponseKey_KeyUp(object sender, KeyEventArgs e)
        {
            if (checkBoxResponseKey.Checked)
            {
                
                checkBoxResponseKey.Tag = e.KeyCode;
                e.Handled = true;
                SaveSettings();
                checkBoxResponseKey.Checked = false;
                
            }
        }


        private void UpdateCheckBoxText()
        {
            checkBoxResponseKey.Text = string.Format(Messages.ResourceManager.GetString("Respose Key '{0}', Click to configure another key".ToValidIdentifier()), checkBoxResponseKey.Tag);
        }

        private void checkBoxResponseKey_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBoxResponseKey.Checked)
            {
                checkBoxResponseKey.Text = Messages.ResourceManager.GetString("Press the key...".ToValidIdentifier());
            }
            else
            {
                UpdateCheckBoxText();
            }
        }

        private void toolStripMenuItemAnswer_Click(object sender, EventArgs e)
        {
            using (var input = new FormReponseInput())
            {
                if (input.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    Answer(input.Interval);
                }
            }
        }

        private void toolStripMenuItemStatistics_Click(object sender, EventArgs e)
        {
            using (var formStats = new FormStatistics(statistics))
            {
                formStats.ShowDialog();
            }
        }
    }
}
